/**
 * Copyright (c) 2008 Johannes Tacskovics
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package ca.johannes.display {
	import flash.accessibility.AccessibilityImplementation;
	import flash.accessibility.AccessibilityProperties;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.Graphics;
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.display.Scene;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.Transform;
	import flash.media.SoundTransform;
	import flash.text.TextSnapshot;
	import flash.ui.ContextMenu;
	
    //--------------------------------------
    //  Class description
    //--------------------------------------
	/**
	 * The ProxyClip class can be used as a proxy for MovieClips that stores method
	 * and property calls made to a ProxyClip object. Those calls can then be later 
	 * applied to any MovieClip that is passed to the ProxyClip object via the 
	 * <code>movieClip<code> setter of the ProxyClip class. 
	 * 
	 * <p>After a MovieClip object has been set all calls made to the ProxyClip
	 * object are passed directly to the MovieClip that was set in the 
	 * <code>movieClip<code> property of the ProxyClip object.</p>
     *
     * @see flash.display.MovieClip
     *
     * @langversion 3.0
     * @playerversion Flash 9.0.124.0
	 */	
	public class ProxyClip extends MovieClip {
		protected var _movieClip:MovieClip;
		protected var _isProxy:Boolean = true;
		protected var commands:Array = new Array();
		protected var listeners:Array = new Array();
		/**
         * Creates a new ProxyClip instance.
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		public function ProxyClip() {
			_movieClip = new MovieClip();
		}
		/**
         * Returns a boolean describing if the ProxyClip object is storing method
         * and property calls, or passing them directly to the MovieClip object set
         * in the <code>movieClip</code> setter. <code>true<code> is returned if the
         * <code>movieClip</code> setter has not yet been set, otherwise
         * <code>false<code> is returned.
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		public function get isProxy():Boolean {
			return _isProxy;
		}
		/**
         * Sets the MovieClip instance that receives all the calls stored by the ProxyClip
         * object. The MovieClip instance can only be set once. If the ProxyClip object has
         * been added to the display list, the ProxyClip object is removed from the display
         * list and replaced by the MovieClip object passed into this setter.
         * 
         * @param value the MovieClip object that recieves all stored calls.
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		public function set movieClip(value:MovieClip):void {
			if(!isProxy){
				return;
			}
			_isProxy = false;
			executeCommands(listeners);
			
			if(_movieClip.stage != null){
				var parent:DisplayObjectContainer = _movieClip.parent;
				var index:int = parent.getChildIndex(_movieClip);
				parent.removeChild(_movieClip);
				_movieClip = null;
				parent.addChildAt(value, index);
			} else {
				_movieClip = null;
			}
			
			_movieClip = value;	
					
			executeCommands(commands);
		}
		/**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		private function executeCommands(commandList:Array):void {		
			while (commandList.length > 0){
				var command:ICommand = commandList.shift();
				command.execute();
			}
			commandList = null;
		}
		/**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		private function callProperty(methodName:String, ...args):* {
			if (args[0] is Array){
				args = args[0];
			}
			
			if(isProxy){
				commands.push(new methodCommand(callProperty, methodName, args));
				if(methodName == "addEventListener"){
					listeners.push(new methodCommand(callProperty, "removeEventListener", [args[0], args[1], args[2]]));
				}
			}

			var method:Function = _movieClip[methodName] as Function;
			return method.apply(_movieClip, args);
		}
		/**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		private function getProperty(name:String):* {
			return _movieClip[name];
		}
		/**
         * @private (protected)
         *
         * @langversion 3.0
         * @playerversion Flash 9.0.124.0
		 */
		private function setProperty(name:String, value:*):void {
			if(isProxy){
				commands.push(new propertyCommand(setProperty, name, value));
			}
			_movieClip[name] = value;
		}
		
	    //------------------------------------------------------------------------------------------------------------
	    //  Complete ActionScript 3.0 Flash Player 9.0 API for flash.display.MovieClip. 
	    //  All methods and properties are overriden and channeled through getProperty, setProperty, or callProperty.
	    //  Functionality remains the same.
	    //------------------------------------------------------------------------------------------------------------
		
		override public function get accessibilityProperties():AccessibilityProperties {
			return getProperty("accessibilityProperties");
		}
		
		override public function set accessibilityProperties(value:AccessibilityProperties):void {
			setProperty("AccessibilityProperties", value);
		}
		
		override public function get accessibilityImplementation():AccessibilityImplementation {
			return getProperty("accessibilityImplementation");
		}
		
		override public function set accessibilityImplementation(value:AccessibilityImplementation):void {
			setProperty("accessibilityImplementation", value);
		}
		
		override public function get alpha():Number {
			return getProperty("alpha");
		}
		
		override public function set alpha(value:Number):void {
			setProperty("alpha", value);
		}
		
		override public function get blendMode():String {
			return getProperty("blendMode");
		}
		
		override public function set blendMode(value:String):void {
			setProperty("blendMode", value);
		}
		
		override public function get buttonMode():Boolean {
			return getProperty("buttonMode");
		}
		
		override public function set buttonMode(value:Boolean):void {
			setProperty("buttonMode", value);
		}
		
		override public function get cacheAsBitmap():Boolean {
			return getProperty("cacheAsBitmap");
		}
		
		override public function set cacheAsBitmap(value:Boolean):void {
			setProperty("cacheAsBitmap", value);
		}
		
		override public function get contextMenu():ContextMenu {
			return getProperty("contextMenu");
		}
		
		override public function set contextMenu(value:ContextMenu):void {
			setProperty("contextMenu", value);
		}
		
		override public function get currentFrame():int {
			return getProperty("currentFrame");
		}
		
		override public function get currentLabel():String {
			return getProperty("currentLabel");
		}
		
		override public function get currentLabels():Array {
			return getProperty("currentLabels");
		}
		
		override public function get currentScene():Scene {
			return getProperty("currentScene");
		}
		
		override public function get doubleClickEnabled():Boolean {
			return getProperty("doubleClickEnabled");
		}
		
		override public function set doubleClickEnabled(value:Boolean):void {
			setProperty("doubleClickEnabled", value);
		}
		
		override public function get dropTarget():DisplayObject {
			return getProperty("dropTarget");
		}

		override public function get enabled():Boolean {
			return getProperty("enabled");
		}
		
		override public function set enabled(value:Boolean):void {
			setProperty("enabled", value);
		}
		
		override public function get filters():Array {
			return getProperty("filters");
		}
		
		override public function set filters(value:Array):void {
			setProperty("filters", value);
		}
		
		override public function get focusRect():Object {
			return getProperty("focusRect");
		}
		
		override public function set focusRect(value:Object):void {
			setProperty("focusRect", value);
		}
		
		override public function get framesLoaded():int {
			return getProperty("framesLoaded");
		}
		
		override public function get graphics():Graphics {
			return getProperty("graphics");
		}
		
		override public function get height():Number {
			return getProperty("height");
		}
		
		override public function set height(value:Number):void {
			setProperty("height", value);
		}
		
		override public function get hitArea():Sprite {
			return getProperty("hitArea");
		}
		
		override public function set hitArea(value:Sprite):void {
			setProperty("hitArea", value);
		}
		
		override public function get loaderInfo():LoaderInfo {
			return getProperty("loaderInfo");
		}
		
		override public function get mask():DisplayObject {
			return getProperty("mask");
		}
		
		override public function set mask(value:DisplayObject):void {
			setProperty("mask", value);
		}
		
		override public function get mouseChildren():Boolean {
			return getProperty("mouseChildren");
		}
		
		override public function set mouseChildren(value:Boolean):void {
			setProperty("mouseChildren", value);
		}
		
		override public function get mouseEnabled():Boolean {
			return getProperty("mouseEnabled");
		}
		
		override public function set mouseEnabled(value:Boolean):void {
			setProperty("mouseEnabled", value);
		}
		
		override public function get mouseX():Number {
			return getProperty("mouseX");
		}
		
		override public function get mouseY():Number {
			return getProperty("mouseY");
		}
		
		override public function get name():String {
			return getProperty("name");
		}
		
		override public function set name(value:String):void {
			setProperty("name", value);
		}
		
		override public function get numChildren():int {
			return getProperty("numChildren");
		}
		
		override public function get opaqueBackground():Object {
			return getProperty("opaqueBackground");
		}
		
		override public function set opaqueBackground(value:Object):void {
			setProperty("opaqueBackground", value);
		}
		
		override public function get parent():DisplayObjectContainer {
			return getProperty("parent");
		}
		
		override public function get root():DisplayObject {
			return getProperty("root");
		}
		
		override public function get rotation():Number {
			return getProperty("rotation");
		}
		
		override public function set rotation(value:Number):void {
			setProperty("rotation", value);
		}
		
		override public function get scale9Grid():Rectangle {
			return getProperty("scale9Grid");
		}
		
		override public function set scale9Grid(value:Rectangle):void {
			setProperty("scale9Grid", value);
		}
		
		override public function get scaleX():Number {
			return getProperty("scaleX");
		}
		
		override public function set scaleX(value:Number):void {
			setProperty("scaleX", value);
		}		
		
		override public function get scaleY():Number {
			return getProperty("scaleY");
		}
		
		override public function set scaleY(value:Number):void {
			setProperty("scaleY", value);
		}
		
		override public function get scenes():Array {
			return getProperty("scenes");
		}
		
		override public function get scrollRect():Rectangle {
			return getProperty("scrollRect");
		}
		
		override public function set scrollRect(value:Rectangle):void {
			setProperty("scrollRect", value);
		}	
		
		override public function get soundTransform():SoundTransform {
			return getProperty("soundTransform");
		}
		
		override public function set soundTransform(value:SoundTransform):void {
			setProperty("soundTransform", value);
		}
		
		override public function get stage():Stage {
			return getProperty("stage");
		}
		
		override public function get tabChildren():Boolean {
			return getProperty("tabChildren");
		}
		
		override public function set tabChildren(value:Boolean):void {
			setProperty("tabChildren", value);
		}	
		
		override public function get tabEnabled():Boolean {
			return getProperty("tabEnabled");
		}
		
		override public function set tabEnabled(value:Boolean):void {
			setProperty("tabEnabled", value);
		}
		
		override public function get tabIndex():int {
			return getProperty("tabIndex");
		}
		
		override public function set tabIndex(value:int):void {
			setProperty("tabIndex", value);
		}
		
		override public function get textSnapshot():TextSnapshot {
			return getProperty("textSnapshot");
		}
		
		override public function get totalFrames():int {
			return getProperty("totalFrames");
		}
		
		override public function get trackAsMenu():Boolean {
			return getProperty("trackAsMenu");
		}
		
		override public function set trackAsMenu(value:Boolean):void {
			setProperty("trackAsMenu", value);
		}
		
		override public function get transform():Transform {
			return getProperty("transform");
		}
		
		override public function set transform(value:Transform):void {
			setProperty("transform", value);
		}
		
		override public function get useHandCursor():Boolean {
			return getProperty("useHandCursor");
		}
		
		override public function set useHandCursor(value:Boolean):void {
			setProperty("useHandCursor", value);
		}
		
		override public function get visible():Boolean {
			return getProperty("visible");
		}
		
		override public function set visible(value:Boolean):void {
			setProperty("visible", value);
		}
		
		override public function get width():Number {
			return getProperty("width");
		}
		
		override public function set width(value:Number):void {
			setProperty("width", value);
		}	
		
		override public function get x():Number {
			return getProperty("x");
		}
		
		override public function set x(value:Number):void {
			setProperty("x", value);
		}		
		
		override public function get y():Number {
			return getProperty("y");
		}
		
		override public function set y(value:Number):void {
			setProperty("y", value);
		}

		override public function addChild(child:DisplayObject):DisplayObject {
			return callProperty("addChild", child);
		}
		
		override public function addChildAt(child:DisplayObject, index:int):DisplayObject {
			return callProperty("addChildAt", child, index);
		}
		
		override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void {
			callProperty("addEventListener", type, listener, useCapture, priority, useWeakReference);
		}
		
		override public function areInaccessibleObjectsUnderPoint(point:Point):Boolean {
			return callProperty("areInaccessibleObjectsUnderPoint", point);
		}
		
		override public function contains(child:DisplayObject):Boolean {
			return callProperty("contains", child);
		}
		
		override public function dispatchEvent(event:Event):Boolean {
			return callProperty("dispatchEvent", event);
		}
		
		override public function getBounds(targetCoordinateSpace:DisplayObject):Rectangle {
			return callProperty("getBounds", targetCoordinateSpace);
		}
		
		override public function getChildAt(index:int):DisplayObject {
			return callProperty("getChildAt", index);
		}
		
		override public function getChildByName(name:String):DisplayObject {	
			return callProperty("getChildByName", name);
		}
		
		override public function getChildIndex(child:DisplayObject):int {
			return callProperty("getChildIndex", child);
		}
		
		override public function getObjectsUnderPoint(point:Point):Array {
			return callProperty("getObjectsUnderPoint", point);
		}
		
		override public function getRect(targetCoordinateSpace:DisplayObject):Rectangle {
			return callProperty("getRect", targetCoordinateSpace);
		}
		
		override public function globalToLocal(point:Point):Point {
			return callProperty("globalToLocal", point);
		}
		
		override public function gotoAndPlay(frame:Object, scene:String=null):void {	
			callProperty("gotoAndPlay", frame, scene);			
		}
		
		override public function gotoAndStop(frame:Object, scene:String=null):void {
			callProperty("gotoAndStop", frame, scene);
		}
		
		override public function hasEventListener(type:String):Boolean {
			return callProperty("hasEventListener", type);
		}
		
		override public function hitTestObject(obj:DisplayObject):Boolean {
			return callProperty("hitTestObject", obj);
		}
		
		override public function hitTestPoint(x:Number, y:Number, shapeFlag:Boolean=false):Boolean {
			return callProperty("hitTestPoint", x, y, shapeFlag);
		}
		
		override public function localToGlobal(point:Point):Point {
			return callProperty("localToGlobal", point);
		}
		
		override public function nextFrame():void {
			callProperty("nextFrame");
		}
		
		override public function nextScene():void {
			callProperty("nextScene");
		}
		
		override public function play():void {
			callProperty("play");
		}
		
		override public function prevFrame():void {
			callProperty("prevFrame");
		}
		
		override public function prevScene():void {
			callProperty("prevScene");
		}
		
		override public function removeChild(child:DisplayObject):DisplayObject {
			return callProperty("removeChild", child);
		}
		
		override public function removeChildAt(index:int):DisplayObject {
			return callProperty("removeChildAt", index);
		}

		override public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void {
			callProperty("removeEventListener", type, listener, useCapture);
		}
		
		override public function setChildIndex(child:DisplayObject, index:int):void {
			callProperty("setChildIndex", child, index);
		}
		
		override public function startDrag(lockCenter:Boolean=false, bounds:Rectangle=null):void {
			callProperty("startDrag", lockCenter, bounds);
		}
		
		override public function stop():void {
			callProperty("stop");
		}
		
		override public function stopDrag():void {
			callProperty("stopDrag");
		}
		
		override public function swapChildren(child1:DisplayObject, child2:DisplayObject):void {
			callProperty("swapChildren", child1, child2);
		}
		
		override public function swapChildrenAt(index1:int, index2:int):void {
			callProperty("swapChildrenAt", index1, index2);
		}
		
		override public function toString():String {
			return callProperty("toString");
		}

		override public function willTrigger(type:String):Boolean {
			return callProperty("willTrigger", type);
		}
	}
}

/**
 * Internal helper interface and classes that define method and property commands. 
 * Commands are stored by the ProxyClip class and then applied to the MovieClip class.
 * @private
 */
internal interface ICommand {
	function execute():*;
}

internal class methodCommand implements ICommand {
	private var method:Function;
	private var methodName:String;
	private var args:Array;
	public function methodCommand(method:Function, methodName:String, args:Array){
		this.method = method;
		this.methodName = methodName;
		this.args = args;
	}
	public function execute():*{
		return method(methodName, args);
	}
}

internal class propertyCommand implements ICommand {
	private var method:Function;
	private var name:String;
	private var value:*;
	public function propertyCommand(method:Function, name:String, value:*){
		this.method = method;
		this.name = name;
		this.value = value;
	}
	public function execute():*{
		return method(name, value);
	}
}